namespace Xmef.Engine.Internal
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using Xmef.Engine;
    using Xmef.Interfaces;
    using Xmef.Library;
    using System.Text;
    using Xmef.Serializer;

    [Serializable]
    internal sealed class PluginContext : PluginIdentity, IPluginContextInternal, IPluginIdentity
    {
        private static readonly IXmlStringSerializer<byte[]> serializer = new XmlBinaryStringSerializer<byte[]>();

        private readonly IPluginAssemblyActivatorInternal _activator;
        private readonly string _applicationPath;
        private readonly string _fileName;
        private readonly byte[] _publicKeyToken;
        private Hashtable _hashtable = new Hashtable();
        private readonly IPluginHostInternal _host;
        private readonly IXmlPluginConfiguration _plugin;

        internal PluginContext(PluginHost host, IXmlPluginConfiguration plugin)
        {
            this._host = host;
            if (!string.IsNullOrEmpty(plugin.Name))
            {
                this._plugin = plugin;
                try
                {
                    this._applicationPath = AppDomain.CurrentDomain.BaseDirectory;
                    this._fileName = Path.Combine(this._applicationPath, string.Format(CultureInfo.InvariantCulture, "{0}.dll", new object[] { plugin.Name }));
                    this._publicKeyToken = GetPublicKeyToken(plugin.PublicKeyToken);
                }
                catch (ArgumentNullException ex)
                {
                    throw new PluginDomainException(ex);
                }
                catch (ArgumentException ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            this._activator = new PluginObjectActivator(string.Format(CultureInfo.InvariantCulture, "[{0},{1}]", new object[] { this._host.HostName, plugin.Name }));
        }

        private byte[] GetPublicKeyToken(string publicKeyToken)
        {
            byte[] result = serializer.Deserialize(publicKeyToken);
            if (result == null) 
            {
                result = new byte[0];
            }
            return result;
        }

        private string SetPublicKey(byte[] data)
        {
            return serializer.Serialize(data);
        }

        bool IPluginContextInternal.Load()
        {
            if (!this._activator.IsActive)
            {
                this._activator.Activate();
                try
                {
                    return this._activator.LoadAssembly(this._fileName, this._publicKeyToken);
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return false;
        }

        IPluginObject<T> IPluginContextInternal.Load<T>(Type type)
        {
            if (this._activator.IsActive)
            {
                try
                {
                    Guid typeId = type.GUID;
                    IPluginObject<T> obj = null;
                    IPluginContextInternal context = this;
                    List<Type> types = new List<Type>(type.BaseType.GetInterfaces());
                    if (this._hashtable.Contains(typeId))
                    {
                        return (this._hashtable[typeId] as IPluginObject<T>);
                    }
                    if (!types.Contains(typeof(T)))
                    {
                        obj = new PluginObject<T>(type, context);
                        this._hashtable.Add(typeId, obj);
                        LogHelper.Write(string.Format(CultureInfo.InvariantCulture, "add object interface={0} type={1} : {2}", new object[] { typeof(T).Name, type.Name, type.BaseType.Name }));
                    }
                    return obj;
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return null;
        }

        bool IPluginContextInternal.Unload()
        {
            if (this._activator.IsActive)
            {
                this._activator.Deactivate();
                try
                {
                    return this._activator.UnloadAssembly(this._fileName, this._publicKeyToken);
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return false;
        }

        IPluginObject<T> IPluginContextInternal.Unload<T>(Type type)
        {
            if (this._activator.IsActive)
            {
                try
                {
                    Guid typeId = type.GUID;
                    IPluginObject<T> obj = null;
                    List<Type> types = new List<Type>(type.BaseType.GetInterfaces());
                    if (this._hashtable.Contains(typeId) && !types.Contains(typeof(T)))
                    {
                        obj = this._hashtable[typeId] as IPluginObject<T>;
                        this._hashtable.Remove(typeId);
                        LogHelper.Write(string.Format(CultureInfo.InvariantCulture, "remove object interface={0} type={1} : {2} ", new object[] { typeof(T).Name, type.Name, type.BaseType.Name }));
                    }
                    return obj;
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return null;
        }

        void IPluginContextInternal.Unload(IPluginObjectInternal obj)
        {
            if (this._activator.IsActive)
            {
                Type interfaceType = obj.InterfaceType;
                Type objectType = obj.ObjectType;
                Guid typeId = objectType.GUID;
                if (this._hashtable.Contains(typeId))
                {
                    this._hashtable.Remove(typeId);
                    LogHelper.Write(string.Format(CultureInfo.InvariantCulture, "remove object interface={0} type={1} base={2} ", new object[] { interfaceType.Name, objectType.Name, objectType.BaseType.Name }));
                }
            }
        }

        IPluginAssemblyInternal IPluginContextInternal.Assembly
        {
            get
            {
                return this._activator.Assembly;
            }
        }

        IXmlPluginConfiguration IPluginContextInternal.Configuration
        {
            get
            {
                return this._plugin;
            }
        }

        IPluginHostInternal IPluginContextInternal.Host
        {
            get
            {
                return this._host;
            }
        }

        bool IPluginContextInternal.IsLoaded
        {
            get
            {
                return this._activator.IsActive;
            }
        }
    }
}

